/*
* The MIT License (MIT)
*
* Copyright (c) 2015 Almex
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
package be.raildelays.batch.skip;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.batch.core.step.skip.SkipLimitExceededException;
import org.springframework.batch.core.step.skip.SkipPolicy;
import org.springframework.dao.DataIntegrityViolationException;
import javax.persistence.PersistenceException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.Locale;
import java.util.function.Function;
/**
* This {@link org.springframework.batch.core.step.skip.SkipPolicy} is dedicated to determine if we have an exceptional
* duplicate key exception coming from the database when we insert new {@code LineStop}.
* The {@code Exception} that we should match would be the {@link org.hibernate.exception.ConstraintViolationException} or
* the {@link java.sql.SQLIntegrityConstraintViolationException} but unfortunately Hibernate convert this {@code class} into
* more generic one called {@link javax.persistence.PersistenceException}.
*
* @author Almex
* @since 1.2
*/
public class SkipUniqueKeyViolationPolicy implements SkipPolicy {
public static final String[] CONSTRAINT_NAMES = {
"LineStopUniqueBusinessKeyConstraint".toUpperCase(),
"TrainLineUniqueBusinessKeyConstraint".toUpperCase()
};
@Override
public boolean shouldSkip(Throwable t, int skipCount) throws SkipLimitExceededException {
boolean result = false;
if (skipCount < 0 || isExpectedViolation(t)) {
result = true; // We gracefully skip in case where the caller of this method gives us skipCount<0
}
return result;
}
private static boolean isExpectedViolation(Throwable e) {
boolean violated = false;
if (e instanceof ConstraintViolationException) {
// We must ignore accent (we use Local.ENGLISH for that) and case
violated = matchAnyViolationNames(
((ConstraintViolationException) e).getConstraintName().toUpperCase(Locale.ENGLISH)::equals
);
} else if (e instanceof SQLIntegrityConstraintViolationException) {
// We must ignore accent (we use Local.ENGLISH for that) and case
violated = matchAnyViolationNames(e.getMessage().toUpperCase(Locale.ENGLISH)::contains);
} else if (e instanceof PersistenceException && e.getCause() != null ||
e instanceof DataIntegrityViolationException) {
/**
* We are in the case where Hibernate encapsulate the Exception into a PersistenceException.
* Then we must check recursively into causes.
*/
violated = isExpectedViolation(e.getCause());
}
return violated;
}
private static boolean matchAnyViolationNames(Function<String, Boolean> check) {
boolean match = false;
for (String constraintName : CONSTRAINT_NAMES) {
match = check.apply(constraintName);
if (match) {
break;
}
}
return match;
}
}